﻿/*
ParallelPlatform Project - A web platform to distribute and share knowledge
Online Editor - An online tool to create and play animations
Copyright (C) 2011 Daniel Guerrero Martínez & Sergio Rodríguez Lumley

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package Classes{

	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	public class CanvasPaintHandler extends CanvasDefaultHandler {
		private static var LINEWIDTH:Number = 5;

		private var paintWidget:PaintWidget; // Painting toolbox
		private var drawLines:	Vector.<PaintLine>; // Of MovieClips
		private var currColor:	uint;
		private var currTool:	uint;
		private var mousedown:	Boolean;
		private var verify:		Boolean;
		private var currLine:	PaintLine;
		
		private var undoRecord: Array; // Array<Array[paintLine, TOOL]>
		private var redoRecord: Array; // Array<Array[paintLine, TOOL]>
		
		// Static variables for paintWidget to use
		public static var SET_COLOR:	uint = 0; // Uses color(uint) as param
		public static var SET_TOOL:		uint = 1; // Uses tool(uint) as param, use static values of this class
		public static var SET_FINISHED:	uint = 2; // Uses no param at all
		
		public static var TOOL_PAINT:	uint = 0;
		public static var TOOL_RUBBER:	uint = 1;
		
		/**
		 Constructor
		*/
		function CanvasPaintHandler(c:CanvasCore){
			super(c);
			paintWidget = new PaintWidget();
			undoRecord	= new Array();
			redoRecord	= new Array();
			
			mousedown		= false;
			currColor		= 0xFF0000; // Red color by default
			currTool		= TOOL_PAINT; // Paint by default
			
			paintWidget.x 	= 350;
			paintWidget.y 	= -30;
			paintWidget.setParentFunction(run);
			
			core.addWidget(paintWidget);
		}
		
		override public function activate(param = null):void{
			if(!activated){
				activated = true;
				if(param != null) shapeToCreate = param;
				core.removeSelections();
				checkUndoRedo();
				
				core.showBlockPanel(true);
				core.getBlockPanel().useHandCursor = false;
				core.getBlockPanel().mouseChildren = true;
				core.getBlockPanel().graphics.clear();
				core.getBlockPanel().graphics.beginFill(0xFFFFFF, 0.4);
				core.getBlockPanel().graphics.drawRect(0, 0, core.getBlockPanel().width, core.getBlockPanel().height);
				core.getBlockPanel().graphics.endFill();
				core.getBlockPanel().addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
				
				if(drawLines == null){
					drawLines = new Vector.<PaintLine>();
				}
				showPaintTool(true);
			}
		}
		
		override public function deactivate():void{
			if(activated){
				activated = false;
				
				if(verify){ // We were editing
					mouseUp(null);
				}
				
				if(drawLines.length > 0){
					finishEditing();
				}
				
				core.getBlockPanel().graphics.clear();
				core.getBlockPanel().useHandCursor = true;
				core.getBlockPanel().mouseChildren = false;
				core.getBlockPanel().removeEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
				showPaintTool(false);
			}
		}
		
		/**
		  Checks whether it is possible to undo or redo, this is only
		  for stetical purposes
		*/
		override public function checkUndoRedo():void{
			core.setUndoAvailable(undoRecord.length > 0); 
			core.setRedoAvailable(redoRecord.length > 0);
		}
		
		/**
		  Indicates if this handler keeps a record of the actions it is doing
		*/
		override public function hasRecord():Boolean{
			return true;
		}
		
		override public function mouseMove(evt:MouseEvent):void{
			// Detect if painting or erasing
			if(mousedown){
				verify = true;
				if(currTool == TOOL_PAINT){
					currLine.addPoint(evt.stageX, evt.stageY);
				}else if(currTool == TOOL_RUBBER){
					
				}
			}
		}
		
		override public function mouseUp(evt:MouseEvent):void{
			// Finish painting or erasing
			if(mousedown && verify){
				if(currTool == TOOL_PAINT){
					//core.getBlockPanel().removeChild(currLine);
					// Optimize currLine erasing all irrelevant points,
					// this can be done getting the orientation vector
					// between two points and removing those that have
					// less than 5º of difference
					currLine.optimize();
					drawLines.push(currLine);
					currLine.addEventListener(MouseEvent.MOUSE_MOVE, deleteLineRubber, false, 1);
					
					addUndoAction(currLine, TOOL_PAINT);
				}
			}
			verify = false;
			mousedown = false;
		}
		
		/**
		  Redoes the last undone action (if there is one)
		*/
		override public function redo():void{
			var action:Array = redoRecord.pop();
			switch(action[1]){
				case TOOL_PAINT:
					addLine(action[0]);
					break;
				case TOOL_RUBBER:
					deleteLine(action[0]);
					break;
			}
			undoRecord.push(action);
			checkUndoRedo();
		}
		
		/**
		  Undoes the last undone action (if there is one)
		*/
		override public function undo():void{
			var action:Array = undoRecord.pop();
			switch(action[1]){
				case TOOL_PAINT:
					deleteLine(action[0]);
					break;
				case TOOL_RUBBER:
					addLine(action[0]);
					break;
			}
			redoRecord.push(action);
			checkUndoRedo();
		}
		
		// -------------- PRIVATE FUNCTIONS ----------------//
		
		/**
		  Adds the specified line to the block panel and to the list
		  of drawLines
		*/
		private function addLine(line:PaintLine):void{
			core.getBlockPanel().addChild(line);
			drawLines.push(line);
			line.addEventListener(MouseEvent.MOUSE_MOVE, deleteLineRubber, false, 1);
		}
		
		/**
		  Adds an undo action to the records, cleaning redoRecord
		  @param line PaintLine line created or erased
		  @param tool uint Tool used to the specified line
		*/
		private function addUndoAction(line:PaintLine, tool:uint):void{
			undoRecord.push([line, tool]);
			
			if(redoRecord.length > 0) redoRecord = new Array();
			checkUndoRedo();
		}
		
		/**
		  Removes all events associated to this line
		  @param line PaintLine to remove the events
		*/
		private function clearLine(line:PaintLine):void{
			line.removeEventListener(MouseEvent.MOUSE_MOVE, deleteLineRubber, false);
		}
		
		/**
		  Removes the specified line from drawLines
		  @param line PaintLine to remove
		*/
		private function deleteLine(line:PaintLine):void{
			core.getBlockPanel().removeChild(line);
			drawLines.splice(drawLines.indexOf(line), 1);
			clearLine(line);
		}
		
		/**
		  Safely removes the selected PaintLine which triggered this event
		*/
		private function deleteLineRubber(evt:MouseEvent):void{
			if(mousedown && verify && currTool == TOOL_RUBBER){
				var pline:PaintLine = evt.currentTarget as PaintLine;
				deleteLine(pline);
				addUndoAction(pline, TOOL_RUBBER);
			}
		}
		
		/**
		  Empties drawLines removing all event listeners before
		  anything is done
		*/
		private function emptyDrawLines():void{
			for each (var line:PaintLine in drawLines){
				core.getBlockPanel().removeChild(line);
				clearLine(line);
			}
			drawLines = new Vector.<PaintLine>();
		}
		
		/**
		  Finish editing the current shape, compiling all lines
		  and adding this action to the action list
		*/
		private function finishEditing():void{
			if(verify) mouseUp(null);
			if(drawLines.length > 0){
				var center:		Point;
				var dimension:	Point;
				var linesArray:	Array = new Array();
				var colorArray:	Array = new Array();
				var numcoloredlines:uint = 0;
				var shape:		ShapeObject;
				var array:		Array;
				
				center = getLinesCenterAndSort();
				
				// Calculate the center of each line and build
				// the color and line arrays for ShapeObject
				for(var i:uint = 0; i<drawLines.length; ++i){
					drawLines[i].centerIn(center);
					linesArray.push(drawLines[i].getPointsArray());
					// Calculate colors
					++numcoloredlines;
					if(((i < drawLines.length-1) && (drawLines[i].getColor() != drawLines[i+1].getColor())) ||
						(i >= drawLines.length-1)){
						colorArray.push(new Point(drawLines[i].getColor(), numcoloredlines));
						numcoloredlines = 0;
					}
				}
				
				// We create the shape
				shape = core.addShapeObjXY(center.x, center.y, shapeToCreate);
				core.setShapePaint(shape, linesArray, colorArray, false); // Not the first action of the cluster of actions
				
				
				// Now that we have included these lines as a shapeObject,
				// we remove all of them
				this.emptyDrawLines();
				
				undoRecord	= new Array();
				redoRecord	= new Array();
				checkUndoRedo();
			}
		}
		
		/**
		  Returns the center of all the lines stored in drawLines and
		  *[sorts the lines grouping them by color] feature removed for now,
		  it is assumed that drawLines has lines stored
		  @return Point with the center of all the lines
		*/
		private function getLinesCenterAndSort():Point{
			var leftTop:	Point = new Point(Number.MAX_VALUE, Number.MAX_VALUE);
			var rightBottom:Point = new Point(Number.MIN_VALUE, Number.MIN_VALUE);
			var tmp:		Point;
			var tmpline:	PaintLine;
			var j:			uint = 0;
			var found:		Boolean = false;
			
			// Get the right values of the corners
			for (var i:uint = 0; i<drawLines.length; ++i){
				
				// THIS FEATURE HAS BEEN REMOVED
				// -- We do not want to reorder by color, as this may imply that some lines
				// will be drawn over others, so maybe this is not what the user wants.
				// until we find a way of avoiding this, no reorder will be performed
				// -------------------------------------------------------------------
				// We sort in a way that is less susceptible of being cuadratic
				//found = false;
//				if(i+1 < drawLines.length && 
//				   drawLines[i+1].getColor() != drawLines[i].getColor()){
//					for(j = i+2; j < drawLines.length && found == false; ++j){
//						if(drawLines[j].getColor() == drawLines[i].getColor()){
//							found = true;
//							// We swap it
//							tmpline = drawLines[j];
//							drawLines[j] = drawLines[i+1];
//							drawLines[i+1] = tmpline;
//						}
//					}
//				}
				
				tmp = drawLines[i].getMaxPoint();
				if(tmp.x > rightBottom.x) rightBottom.x = tmp.x;
				if(tmp.y > rightBottom.y) rightBottom.y = tmp.y;
				
				tmp = drawLines[i].getMinPoint();
				if(tmp.x < leftTop.x) leftTop.x = tmp.x;
				if(tmp.y < leftTop.y) leftTop.y = tmp.y;
			}
			// We calculate the new center of the shape
			tmp.x = (leftTop.x + rightBottom.x)/2;
			tmp.y = (leftTop.y + rightBottom.y)/2;
			
			return tmp;
		}
		
		/**
		  Mouse has been pushed down on front panel
		*/
		private function mouseDown(evt:MouseEvent):void{
			// Start painting or erasing
			if(! verify){
				if(currTool == TOOL_PAINT){
					currLine  = new PaintLine();
					currLine.x = 0;
					currLine.y = 0;
					currLine.useHandCursor = false;
					currLine.buttonMode = false;
					currLine.setColor(currColor);
					core.getBlockPanel().addChild(currLine);
				}
				mousedown = true;
			}
		}
		
		/**
		  Runs an action to change the state of this handler
		  @param action uint specifying the kind of action to
		  	run, these are static values of this class, starting
			with "SET_"
		  @param param <undefined> Parameter depending on the kind
		  	of action, see static values to check wich are needed
		*/
		private function run(action:uint, param=null):void{
			switch(action){
				case SET_COLOR:
					currColor = param as uint;
					break;
				case SET_TOOL:
					if(verify) mouseUp(null);
					currTool = param as uint;
					break;
				case SET_FINISHED:
					finishEditing();
					break;
			}
		}
		
		/**
		  Shows or hides paintWidget
		  @param doShow Boolean true to show, false to hide it (true by default)
		*/
		private function showPaintTool(doShow:Boolean = true):void{
			if(doShow){
				paintWidget.y = 0;
			}else{
				paintWidget.y = -30;
			}
		}
		
	}
}